<?php
/***
 * Candy框架 中间件基类
 * 
 * $Author: 刘森 (fingerboy@qq.com) $
 * $Date: 2019-07-27 12:55:58 $   
 */

declare (strict_types = 1);
namespace Candy\Core;

defined('CANDY') OR die('You Are A Bad Guy. o_O???');

Class Security {
	private static $app;
	private static $Middlewares;
	private static $blackList;
	private static $whiteList;
	//护盾安全等级
	private static $rank = '';	//.{0,10}
	//原始文件预处理strtolower，bin2hex
	private static $preProcess = ['"."',"'.'"];
    //php代码特征规则
    private static $codePreg = [
		'63#%#68#%#6f#%#77#%#6e.{0,20}(22|27|28)',/*chown*/
		'63#%#68#%#6d#%#6f#%#64.{0,20}(22|27|28)',/*chmod*/
		'65#%#78#%#65#%#63.{0,20}(22|27|28)',/*exec*/
		'65#%#76#%#61#%#6C.{0,20}(22|27|28)',/*eval*/
		'73#%#79#%#73#%#74#%#65#%#6d.{0,20}(22|27|28)',/*system*/
		'63#%#68#%#67#%#72#%#70.{0,20}(22|27|28)',/*chgrp*/
		'73#%#74#%#72#%#5f#%#72#%#6f#%#74#%#31#%#33.{0,20}(22|27|28)',/*str_rot13*/
		'67#%#7A#%#75#%#6E#%#63#%#6F#%#6D#%#70#%#72#%#65#%#73#%#73.{0,20}(22|27|28)',/*gzuncompress*/
		'62#%#61#%#73#%#65#%#36#%#34#%#5F#%#64#%#65#%#63#%#6F#%#64#%#65.{0,20}(22|27|28)',/*base64_decode*/
		'63#%#68#%#72#%#(22|27|28)',/*chr(*/
		'70#%#72#%#69#%#6e#%#74.{0,20}(22|27|28)',/*print(*/
		'3c#%#3f#%#70#%#68#%#70.*3f#%#3e',/*<?php?>*/
		'3c#%#3f.*3f#%#3e',/*<??>*/
		'3c#%#25.*25#%#3e',/*<%%>*/
		'24#%#7b#%#7d',/*${}*/
		'7b7c.*7d',/*{|}*/
		'5c#%#78.*5c#%#78.*5c#%#78.*5c#%#78',/**/
		'26.*5b.*5d.*3d.*\d+.*26',/*&...[]...=...\d...&*/
		'24.{0,40}7b.{0,40}24.{0,40}7b',/*${${*/
		'61#%#73#%#73#%#65#%#72#%#74.{0,20}(22|27|28)',/*assert*/
		'72#%#65#%#67#%#5f#%#72#%#65#%#70#%#6c#%#61#%#63#%#65.{0,20}(22|27|28)',/*reg_replace*/
		'63#%#72#%#65#%#61#%#74#%#65#%#5f#%#66#%#75#%#6e#%#63#%#74#%#69#%#6f#%#6e.{0,20}(22|27|28)',/*create_function*/
		'66#%#69#%#6c#%#65#%#20#%#63#%#6f#%#6e#%#74#%#65#%#6e#%#74#%#73.{0,20}(22|27|28)',/*file_put_contents*/
		//js代码特征规则<[^>]?
		'3c#%#73#%#63#%#72#%#69#%#70#%#74.*3c#%#2f#%#73#%#63#%#72#%#69#%#70#%#74',/*<script\x00</script*/
		'6a#%#61#%#76#%#61#%#73#%#63#%#72#%#69#%#70#%#74#%#3a',/*javascript:*/
		'3c.*6f#%#6e#%#65#%#72#%#72#%#6f#%#72',/*onerror*/
		'3c.*6f#%#6e#%#6c#%#6f#%#61#%#64',/*onload*/
		'3c.*6f#%#6e#%#62#%#6c#%#75#%#72',/*onblur*/
		'3c.*6f#%#6e#%#63#%#68#%#61#%#6e#%#67#%#65',/*onchange*/
		'3c.*6f#%#6e#%#63#%#6c#%#69#%#63#%#6b',/*onclick*/
		'3c.*6f#%#6e#%#64#%#62#%#6c#%#63#%#6c#%#69#%#63#%#6b',/*ondblclick*/
		'3c.*6f#%#6e#%#66#%#6f#%#63#%#75#%#73',/*onfocus*/
		'3c.*6f#%#6e#%#6b#%#65#%#79#%#64#%#6f#%#77#%#6e',/*onkeydown*/
		'3c.*6f#%#6e#%#6b#%#65#%#79#%#70#%#72#%#65#%#73#%#73',/*onkeypress*/
		'3c.*6f#%#6e#%#6b#%#65#%#79#%#75#%#70',/*onkeyup*/
		'3c.*6f#%#6e#%#6d#%#6f#%#75#%#73#%#65#%#64#%#6f#%#77#%#6e',/*onmousedown*/
		'3c.*6f#%#6e#%#6d#%#6f#%#75#%#73#%#65#%#6d#%#6f#%#76#%#65',/*onmousemove*/
		'3c.*6f#%#6e#%#6d#%#6f#%#75#%#73#%#65#%#6f#%#75#%#74',/*onmouseout*/
		'3c.*6f#%#6e#%#6d#%#6f#%#75#%#73#%#65#%#6f#%#76#%#65#%#72',/*onmouseover*/
		'3c.*6f#%#6e#%#6d#%#6f#%#75#%#73#%#65#%#75#%#70',/*onmouseup*/
		'3c.*6f#%#6e#%#72#%#65#%#73#%#65#%#74',/*onreset*/
		'3c.*6f#%#6e#%#72#%#65#%#73#%#69#%#7a#%#65',/*onresize*/
		'3c.*6f#%#6e#%#73#%#65#%#6c#%#65#%#63#%#74',/*onselect*/
		'3c.*6f#%#6e#%#73#%#75#%#62#%#6d#%#69#%#74',/*onsubmit*/
		'3c.*6f#%#6e#%#75#%#6e#%#6c#%#6f#%#61#%#64',/*onunload*/
		'2e#%#63#%#6f#%#6f#%#6b#%#69#%#65.*3d',/*cookie...=*/
		//sql代码特征规则
		'73#%#65#%#6c#%#65#%#63#%#74.*66#%#72#%#6f#%#6d',/*select...from*/
		'75#%#70#%#64#%#61#%#74#%#65.*73#%#65#%#74',/*update...set*/
		'69#%#6e#%#73#%#65#%#72#%#74.*69#%#6e#%#74#%#6f',/*insert...into*/
		'64#%#65#%#6c#%#65#%#74#%#65.*66#%#72#%#6f#%#6d',/*delete...from*/
		'64#%#72#%#6f#%#70.*74#%#61#%#62#%#6c#%#65',/*drop...table*/
		'72#%#65#%#70#%#6c#%#61#%#63#%#65.*69#%#6e#%#74#%#6f',/*replace...into*/
		'74#%#72#%#75#%#6e#%#63#%#61#%#74#%#65.*74#%#61#%#62#%#6c#%#65',/*truncate...table*/
		'73#%#65#%#6c#%#65#%#63#%#74.{0,100}69#%#6e#%#74#%#6f.*6f#%#75#%#74#%#66#%#69#%#6c#%#65',/*select...into...outfile*/
		'6c#%#6f#%#61#%#64.{0,20}64#%#61#%#74#%#61.{0,20}69#%#6e#%#66#%#69#%#6c#%#65',/*load...data...infile*/
		'61#%#6c#%#74#%#65#%#72.*74#%#61#%#62#%#6c#%#65',/*alter...table*/
		'75#%#6e#%#69#%#6f#%#6e.*73#%#65#%#6c#%#65#%#63#%#74',/*union select*/
		'75#%#73#%#65#%#72#%#28.{0,40}29',/*user()*/
		'64#%#61#%#74#%#61#%#62#%#61#%#73#%#65#%#28.{0,40}29',/*database()*/
		'76#%#65#%#72#%#73#%#69#%#6f#%#6e#%#28.{0,40}29',/*user()*/
		'(40)+#%#76#%#65#%#72#%#73#%#69#%#6f#%#6e#%#5f#%#63#%#6f#%#6d#%#70#%#69#%#6c#%#65#%#5f#%#6f#%#73',/*@@version_compile_os*/
		'67#%#72#%#6f#%#75#%#70#%#5f#%#63#%#6f#%#6e#%#63#%#61#%#74.*28',/*group_concat(*/
		'63#%#6f#%#6e#%#63#%#61#%#74#%#5f#%#77#%#73.*28',/*concat_ws(*/
		'73#%#63#%#68#%#65#%#6d#%#61#%#5f#%#6e#%#61#%#6d#%#65',/*schema_name*/
		'74#%#61#%#62#%#6c#%#65#%#5f#%#6e#%#61#%#6d#%#65#%#73',/*table_names*/
		'63#%#6f#%#6c#%#75#%#6d#%#6e#%#5f#%#6e#%#61#%#6d#%#65',/*column_name*/
		'20.{0,4}6c#%#69#%#6b#%#65.{0,4}20.*(22|27)',/* like ("|')*/
		'20.{0,4}69#%#6e.{0,4}20#%#28',/* in (*/
		'(22|27|29|3d|3e|3c|3e3d|3c3d|213d).{0,20}20#%#6f.{0,4}72.{0,4}20',/*('|"|)|=|>|<|>=|<=|!=) or ...*/
		'(22|27|29|3d|3e|3c|3e3d|3c3d|213d).{0,20}20#%#61.{0,6}6e.{0,6}64.{0,4}20',/*('|"|)|=|>|<|>=|<=|!=) and ...*/
		'20.{0,40}62.{0,8}65.{0,8}74.{0,8}77.{0,8}65.{0,8}65.{0,8}6e.{6,80}20#%#61.{0,8}6e.{0,8}64.{0,40}20',/* between..and... */
		'(3e|3c|3e3d|3c3d|3d|213d|29).{0,40}23',/*(>|<|>=|<=|=|!=|))...#等号无法实现过滤暂时*/
		'78#%#70#%#5f#%#63#%#6d#%#64#%#73#%#68#%#65#%#6c#%#6c',/*xp_cmdshell*/
    ];
	
	//免验证令牌应该由后台注册
    private static $Initializekey = 'fHoi[moOij8s9fdsifrewikdsaf';//当是后台时跳过验证
	
	/**
     * 入口函数
     */
    public static function run(): void
	{	
		//加载中间件
		self::loadMiddleware();
	}
	
	/**
     * 运行中间件
     */
    public static function runMiddlewares(string $type = 'Init', string $content = '')
	{
		if(!empty(self::$Middlewares[$type])){
			if($type == 'Over'){
				foreach(self::$Middlewares[$type] as $middleware){
					$content = $middleware->handle($content);
				}
			}else{
				foreach(self::$Middlewares[$type] as $middleware){
					$middleware->handle();
				}
			}
			
		}
		
		if(isDebug() === false && $type == 'After'){
			self::_getInfo();
		}
		
		if($type == 'Over'){
			return $content;
		}
	}
	
	/**
     * 加载中间件
     * @access protected
     */
    protected static function loadMiddleware(): void
    {
		$Middlewares = loadConfig('Middleware');
		self::addMiddlewares($Middlewares);
    }
	
	/**
     * 添加中间件
     * @access public
     * @param array $commands
     */
    protected static function addMiddlewares(array $middlewares): void
    {
		if(!empty($middlewares)){
			foreach ($middlewares as $type => $middlewareList) {
				if(empty($middlewareList)){
					foreach($middlewareList as $key=>$middleware){
						if (is_subclass_of($middleware, Security::class)) {
							//添加中间件
							self::addMiddleware($middleware, is_numeric($key) ? '' : $key, $type);
						}
					}
				}
				
			}
		}
        
    }
	
	/**
     * 添加一个指令
     * @access public
     * @param string|Middleware $middleware 指令对象或者指令类名
     * @param string $name 指令名 留空则自动获取
     */
    public static function addMiddleware($middleware, string $name = '',string $type = ''): void
    {
        if ($name) {
            self::$Middlewares[$type][$name] = $middleware;
        }

        if (is_string($middleware)) {}else{
			self::$Middlewares[$type][$middleware->getName()] = $middleware;
			foreach ($middleware->getAliases() as $alias) {
				self::$Middlewares[$type][$alias] = $middleware;
			}
		}
    }
	
	/**
     * 参数过滤
     */
    private static function _getInfo(): void
	{
		//白名单不做任何过滤
		if(self::check('w') === false){
			//简单过滤
			array_walk($_GET, 'Candy\Core\Security::_checkGet');
			if(C('METHOD') == 'POST'){
				array_walk($_POST, 'Candy\Core\Security::_checkPost');
				if(is_null(C('REMOVEXSS')))
					array_walk($_POST, 'Candy\Core\Security::_removeXss');	//xss过滤函数
			}
			
			//判断来源网址
			self::_misMatch();
			
			//严格过滤
			$rank = C('securityRank');
			if($rank != null){
				self::$rank = $rank;
				array_walk($_SERVER, 'Candy\Core\Security::_recursive');//处理服务器数据
				array_walk($_COOKIE, 'Candy\Core\Security::_recursive');//处理COOKIE数
				if(isset($_COOKIE[self::$Initializekey])){//过检POST数据
					$cookieStr = self::_murlDecodeEx($_COOKIE[self::$Initializekey]);
					if($cookieStr != md5(serialize(self::$Initializekey))){
						array_walk($_POST, 'Candy\Core\Security::_recursive');//处理POST数据
					}
				}else{
					array_walk($_POST, 'Candy\Core\Security::_recursive');//处理POST数据
				}
				array_walk($_GET, 'Candy\Core\Security::_recursive');//处理GET数据
			}
		}
    }
	
    /**
     * 删除令牌所有数据使用验证
	 *
     * @return string 
     */
    public static function _delTokenName(): void
	{
        cookie(self::$Initializekey, null);
    }
	
    /**
     * 设置令牌名称当使用$_POST时跳过验证
     */
    public static function _setTokenName(): void
	{
        cookie(self::$Initializekey, md5(serialize(self::$Initializekey)));
    }
	
    /**
     * 匹配来源网址不同直接退出
     */
    private static function _misMatch(): bool
	{
        $referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];
        if(empty($referer)){ return false; }
        $HOSTARR = [];
        preg_match('/https?:\/\/([^\/]*)/i', $referer, $HOSTARR);
        if(isset($HOSTARR[1])){
            $referer = $HOSTARR[1];
        }else{
			if(count($_POST) > 0)
				self::_unSetPost();
            return false;
        }
        $referer = self::_preProcess($referer);
        $curhost = empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'];
        $curhost = self::_preProcess($curhost);
        if($referer == $curhost){ 
            return true;
        }else{
			if(count($_POST) > 0)
				self::_unSetPost();
            return false;
        }
    }
	
    /**
     * 数据递归处理
	 *
     * @param type $array
     */
    private static function _recursive($varPara)
	{
		if(empty($varPara)) return ;
        if(is_array($varPara)){
			return array_walk($varPara, 'Candy\Core\Security::_recursive');
        }else{
			if(is_string($varPara))
				if(self::_strToHtml($varPara)){
					showTplMsg('firewall', "违规字符串:{$varPara},修改后刷新即可..."); 
				}else{
					return $varPara;
				}
        }
    }
	
    /**
     * 强制卸载POST数据
     */
    private static function _unSetPost(): void
	{
        unset($_POST);
        $_POST['SAFE_ERROR'] = 'Data Unloaded by Firewall!';
    }
	
    /**
     * 字符到实体标签 
	 *
     * @param string $str
     * @return string
     */
    private static function _strToHtml(string $str): bool
	{
        $codeHtml = self::_murlDecodeEx($str);
        $codeHtml = self::_preProcess($codeHtml);
        if(self::_pregCode($codeHtml)){
            return true;
        }       
        return false;
    }
	
    /**
     * 验证代码
	 *
     * @param type $hexStr
     * @return boolean
     */
    private static function _pregCode($hexStr): bool
	{
        foreach(self::$codePreg as $value){
            $value = str_replace('#%#', self::$rank, $value);
            $preg = "/(.*){$value}.*/is";
            $revStr = [];
			preg_match($preg, $hexStr, $revStr);
            if(preg_match($preg, $hexStr, $revStr)){
                if(self::_evenNumber($revStr)){ continue; }
                return true;
            }
        }        
        return false;
    }
	
    /**
     * 判断正则前段是否是偶数个16进制
	 *
     * @param type $pregRevArr
     * @return boolean
     */
    private static function _evenNumber(array $pregRevArr): bool
	{
        if(isset($pregRevArr[1])){
            return false;
        }else{
            return true;
        }
    }
	
    /**
     * url反编译
	 *
     * @return string
     */
    private static function _murlDecodeEx(string $url): string
	{
        $url = urldecode(urldecode(stripslashes(htmlspecialchars_decode(htmlspecialchars_decode($url)))));
        return trim(trim($url,'"'),"'");
    }
	
    /**
     * 预处理字符串
	 *
     * @param string $str
     */
    private static function _preProcess(string $str): string
	{
        $str = str_ireplace(self::$preProcess, '', $str);
        $str = strtolower($str);
        $revStr = '';
        for($i = 0;$i<strlen($str);$i++){ $revStr .= 'x'. bin2hex($str[$i]); }
        return $revStr;
    }
	
	/**
	 *记录$_GET中的非法字符
	 */
	private static function _checkGet($get)
	{
		static $bad = [];
		if(empty($get)) return ;
		if(is_array($get)) return array_walk($get, 'Candy\Core\Security::_checkGet');
		empty($bad) && $bad = loadConfig('AttackCode')['get'] ?? [];
		if(empty($bad)) return self::_removeXss($get);
        foreach ($bad as $t) {
            if(substr_count(strtolower($get), $t) > 0){
				self::_saveAttackLog('GET', $get);
				return ;
			}else{
				return self::_removeXss($get);
			}
        }
	}
	
	/**
	 * 记录$_POST中的非法字符
	 */
	private static function _checkPost($post)
	{
	    static $bad = [];
		if(empty($post)) return ;
		if(is_array($post))  return array_walk($post, 'Candy\Core\Security::_checkGet');
		empty($bad) && $bad = loadConfig('AttackCode')['post'];
		if (empty($bad)) return $post;
        foreach ($bad as $t) {
            if(substr_count(strtolower($post), $t) > 0){
				self::_saveAttackLog('POST', $post);
				return ;
			}else{
				return $post;
			}
        }
	}
	
	/**
	 * 清理 HTML 中的 XSS 潜在威胁 严格模式下，iframe 等元素也会被过滤
	 *
	 * @param string $string
	 * @param bool $strict
	 * @return mixed
	 */
	public static function _removeXss($string): string
	{
		if(empty($string)) return '';
		
		// 移除不可见的字符
		$string = preg_replace('/%0[0-8bcef]/', '', $string);
		$string = preg_replace('/%1[0-9a-f]/', '', $string);
		$string = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $string);

		$string = preg_replace('/<meta.+?>/is', '', $string); // 过滤 meta 标签
		$string = preg_replace('/<link.+?>/is', '', $string); // 过滤 link 标签
		$string = preg_replace('/<script.+?<\/script>/is', '', $string); // 过滤 script 标签
		$string = preg_replace('/<style.+?<\/style>/is', '', $string); // 过滤 style 标签
		$string = preg_replace('/<iframe.+?<\/iframe>/is', '', $string); // 过滤 iframe 标签 1
		$string = preg_replace('/<iframe.+?>/is', '', $string); // 过滤 iframe 标签 2

		$string = preg_replace_callback ( '/(\<\w+\s)(.+?)(?=( \/)?\>)/is', function ($m) {
			// 去除标签上的 on.. 开头的 JS 事件，以下一个 xxx= 属性或者尾部为终点
			$m [2] = preg_replace('/\son[a-z]+\s*\=.+?(\s\w+\s*\=|$)/is', '\1', $m [2]);

			// 去除 A 标签中 href 属性为 javascript: 开头的内容
			if (strtolower ( $m [1]) == '<a ') {
				$m [2] = preg_replace('/href\s*=["\'\s]*javascript\s*:.+?(\s\w+\s*\=|$)/is', 'href="#"\1', $m [2]);
			}

			return $m [1] . $m [2];
		}, $string);

		$string = preg_replace('/(<\w+)\s+/is', '\1 ', $string); // 过滤标签头部多余的空格
		$string = preg_replace('/(<\w+.*?)\s*?( \/>|>)/is', '\1\2', $string); // 过滤标签尾部多余的空格

		return $string;
	}
	
	/**
	 * 保存非法字符攻击日志
	 */
	private static function _saveAttackLog(string $type,string $val): void
	{
		$logpath = LOGPATH . 'attack-' . date('Y-m-d') .'.log';
		
		//攻击记录
		$data = [
			'url'  => isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] ? $_SERVER['QUERY_STRING'] : $_SERVER['REQUEST_URI'],
			'ip'   => N('Client')::getClientIp(),
			'time' => gmTime(),
			'type' => $type,
			'val'  => $val,
			'user' => $_SERVER['HTTP_USER_AGENT'],
		];
		
		$logs = Log::read($logpath);
		if(!empty($logs)){
			$idata = 0;
			foreach ($logs as $v) {
				if (empty($v)) continue;
				$t = unserialize(substr($v,30));
				if ($data['ip'] == $t['ip']) $idata ++;
				//若Ip出现10次以上或相同地址在20秒内都含有非法字符直接加入黑名单
				if ($idata >= 10 || ($data['time'] - $t['time'] < 20 && $data['ip'] == $t['ip'] && $data['url'] == $t['url'])){
					self::add(['type'=>'b','exp_time'=>gmTime() + G('blacklocktime', '', 3600)]);
					$msg = '请勿提交非法'.strtoupper($type).'参数值：' . htmlspecialchars(self::_stripSlashesCheck($val));
					showTplMsg('firewall', $msg);
				}
			}
			unset($logs);
		}
		
		//添加记录
		Log::input(serialize($data), -1, $logpath);
		$msg = '非法'.strtoupper($type).'参数值：' . htmlspecialchars(self::_stripSlashesCheck($val));
		showTplMsg('firewall', $msg);
	}
	
	/**
	 * stripslashes
	 */
	protected static function _stripSlashesCheck(string $string): string
	{
		if (!$string) return null;
		if (!is_array($string)) return stripslashes($string);
		foreach ($string as $key => $value) {					
			$string[$key] = self::_stripSlashesCheck($value);
		}
		return $string;
	}
	
	/**
	 * 获取IP名单
	 */
	public static function IPList(): void
	{
		if(empty(self::$blackList) && empty(self::$whiteList)){
			$blackListFile = LOGPATH .'ip-blackList.log';
			$whiteListFile = LOGPATH .'ip-whiteList.log';
			self::$blackList = file_exists($blackListFile) ? self::textToArray(file_get_contents($blackListFile)) : [];
			self::$whiteList = file_exists($whiteListFile) ? self::textToArray(file_get_contents($whiteListFile)) : [];
		}
	}
	
	/**
	 * 添加
	 * 
	 * @return bool
	 */
	public static function add(array $data = []): bool
	{
		if(empty($data['ip_start'])){
			$data['ip_start'] = \Candy\Extend\Network\Client::getClientIp();
		}
		if(ip2long($data['ip_start']) === false){
			return false;
		}
		if(empty($data['ip_end'])){
			$data['ip_end'] = $data['ip_start'];
		}
		if(ip2long($data['ip_end']) === false){
			return false;
		}
		if(!empty($data['exp_time']) && intval($data['exp_time']) < gmTime()){
			return false;
		}
		$startIPNumber = self::transForm($data['ip_start']);
		$endIPNumber = self::transForm($data['ip_end'],false);
		self::IPList();
		self::remove($data['type'], $startIPNumber, $endIPNumber);
		if(strtolower($data['type']) == 'b'){
			self::$blackList[] = [$startIPNumber, $endIPNumber, $data['exp_time']];
		}
		else{
			self::$whiteList[] = [$startIPNumber, $endIPNumber, $data['exp_time']];
		}
		
		if(!defined('CLIWORKING'))
			self::save($data['type']);
		
		return true;
	}
	
	/**
	 * 移除
	 * 
	 * @return bool
	 */
	public static function delete(array $data = []): bool
	{
		if(empty($data['ip_start'])){
			$data['ip_start'] = \Candy\Extend\Network\Client::getClientIp();
		}
		if(ip2long($data['ip_start'])===false){
			return false;
		}
		if(empty($data['ip_end'])){
			$data['ip_end'] = $data['ip_start'];
		}
		if(ip2long($data['ip_end']) === false){
			return false;
		}
		self::IPList();
		$startIPNumber = self::transForm($data['ip_start']);
		$endIPNumber = self::transForm($data['ip_end'], false);
		self::remove($data['type'], $startIPNumber, $endIPNumber);
		self::save($data['type']);
		return true;
	}
	
	/**
	 * IP名单检测
	 * 
	 * @return bool
	 */
	public static function check(string $type = 'b',string $ip = ''): bool
	{
		if(self::find($ip, $type)){
			return true;
		}
		return false;
	}
	
	/**
	 * IP黑名单检测
	 * 
	 * @return array
	 */
	public static function getAll(string $type = 'b'): array
	{
		$return=[];
		self::IPList();
		$listArray = strtolower($type) == 'b' ? self::$blackList : self::$whiteList;
		foreach($listArray as $val){
			$return[] = [self::transForm($val[0]), self::transForm($val[1]), $val[2]];
		}
		return $return;
	}
	
	/**
	 * 查找
	 * 
	 * @return bool
	 */
	public static function find(string $ip,string $type = 'b'): bool
	{
		if(empty($ip)){
			$ip = \Candy\Extend\Network\Client::getClientIp();
		}
		if(ip2long($ip)===false){
			return false;
		}
		$IPNumber = self::transForm($ip);
		self::IPList();
		$listArray = strtolower($type) == 'b' ? self::$blackList : self::$whiteList;
		foreach($listArray as $val){
			if(($IPNumber == $val[0] || ($IPNumber > $val[0] && $IPNumber < $val[1])) && (gmTime() <= $val[2] || empty($val[2]))){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 清理
	 * 
	 * @return bool
	 */
	public static function clean(string $type = '', $reset = false): void
	{
		if($reset){
			if(strtolower($type) == 'b' || empty($type)){
				self::$blackList = [];
			}
			if(strtolower($type) == 'w' || empty($type)){
				self::$whiteList = [];
			}
		}else{
			self::IPList();
			if(strtolower($type) == 'b' || empty($type)){
				foreach(self::$blackList as $key=>$val){
					if(!empty($val[2]) && intval($val[2]) < gmTime()){
						unset(self::$blackList[$key]);
					}
				}
			}
			if(strtolower($type) == 'w' || empty($type)){
				foreach(self::$whiteList as $key=>$val){
					if(!empty($val[2]) && intval($val[2]) < gmTime()){
						unset(self::$whiteList[$key]);
					}
				}
			}
		}
		if(strtolower($type) == 'b' || empty($type)){
			self::save('b');
		}
		if(strtolower($type) == 'w' || empty($type)){
			self::save('w');
		}
	}
	
	/**
	 * 文字转数组
	 * 
	 * @return array
	 */
	private static function textToArray(string $str): array
	{
		$str = preg_replace('/[^0-9.\-\*,&]/', '', $str);
		$firstStep = explode('&', $str);
		$secondStep = [];
		foreach($firstStep as $key => $val){
			if(!empty($val)){
				$secondStep[$key] = explode(',', $val);
				if(isset($secondStep[$key][1])){
					$secondStep[$key][2] = $secondStep[$key][1];
					$tempArray = explode('-', $secondStep[$key][0]);
					if(isset($tempArray[1])){
						$secondStep[$key][0] = $tempArray[0];
						$secondStep[$key][1] = $tempArray[1];
					}
				}
			}
		}
		return $secondStep;
	}
	
	/**
	 * 数组转文本
	 * 
	 * @return string
	 */
	private static function arrayToText(array $array): string
	{
		$return = '';
		foreach($array as $val){
			if(isset($val[0])){
				if(!isset($val[1])){
					$val[1] = $val[0];
				}
				if(empty($val[2])){
					$val[2] = '';
				}
				if($val[0]>$val[1]){
					$return .= $val[1].'-'.$val[0].','.$val[2].'&';
				}
				else{
					$return .= $val[0].'-'.$val[1].','.$val[2].'&';
				}
			}
		}
		return $return;
	}
	
	/**
	 * 格式检测
	 * 
	 * @return bool
	 */
	private static function ipCheck(string $str): bool
	{
		if(preg_match('/(?=(\b|\D))((\*\.)|(\*)|(25[0-5]|2[0-4]\d|[01]?\d\d?)($|(?!\.$)\.)){4}/',$str)){
			return true;
		}else{
			return false;
		}
	}
	
	/**
	 * 转换
	 * 
	 * @return bool
	 */
	private static function transForm(string $str,bool $start = true){
		if(ctype_digit($str)){
			return long2ip($str);
		}
		$str = $start ? str_replace('*','0',$str) : str_replace('*','255',$str);
		$intIP = ip2long($str);
		if($intIP === false){
			return false;
		}
		return sprintf('%u', $intIP);
	}
	
	/**
	 * 移除
	 * 
	 * @return bool
	 */
	private static function remove($type, $startIPNumber, $endIPNumber): bool
	{
		$listArray = strtolower($type) == 'b' ? self::$blackList : self::$whiteList;
		foreach($listArray as $key=>$val){
			if($startIPNumber == $val[0] && $endIPNumber == $val[1]){
				if(strtolower($type)=='b'){
					unset(self::$blackList[$key]);
				}
				else{
					unset(self::$whiteList[$key]);
				}
			}
		}
		return true;
	}
	
	/**
	 * 写入文件
	 */
	private static function save(string $type = ''): void
	{
		if(strtolower($type) == 'b'){
			$listText = self::arrayToText(self::$blackList);
			$handle = @fopen(LOGPATH .'ip-blackList.log', 'w');
		}else{
			$listText = self::arrayToText(self::$whiteList);
			$handle = @fopen(LOGPATH .'ip-whiteList.log', 'w');
		}
		fwrite($handle, $listText);
		fclose($handle);
	}
}